﻿// ------------------------------------------------------------------------
//  Copyright 2025 The Dapr Authors
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//      http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//  ------------------------------------------------------------------------

using System.Diagnostics.CodeAnalysis;
using Dapr.Common;
using Dapr.DistributedLock.Models;
using Grpc.Core;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;

namespace Dapr.DistributedLock;

/// <summary>
/// A client for performing distributed locking operations with Dapr.
/// </summary>
[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")]
internal sealed class DaprDistributedLockGrpcClient : DaprDistributedLockClient
{
    /// <summary>
    /// A client for performing distributed locking operations with Dapr.
    /// </summary>
    public DaprDistributedLockGrpcClient(Autogenerated.Dapr.DaprClient client, HttpClient httpClient, string daprApiToken) : base(client, httpClient, daprApiToken)
    {
    }

    /// <inheritdoc />
    public override async Task<LockResponse?> TryLockAsync(string storeName, string resourceId, string lockOwner,
        int expiryInSeconds,
        CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrEmpty(storeName);
        ArgumentException.ThrowIfNullOrEmpty(resourceId);
        ArgumentException.ThrowIfNullOrEmpty(lockOwner);

        if (expiryInSeconds <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(expiryInSeconds), expiryInSeconds, "The value cannot be less than or equal to zero.");
        }

        var request = new Autogenerated.TryLockRequest
        {
            StoreName = storeName, ResourceId = resourceId, LockOwner = lockOwner, ExpiryInSeconds = expiryInSeconds
        };

        try
        {
            var grpcCallOptions =
                DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprDistributedLockClient).Assembly, this.DaprApiToken,
                    cancellationToken);
            var response = await this.Client.TryLockAlpha1Async(request, grpcCallOptions);
            return !response.Success
                ? null
                : new LockResponse(this) { StoreName = storeName, ResourceId = resourceId, LockOwner = lockOwner };
        }
        catch (RpcException ex)
        {
            throw new DaprException("Lock operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
        }
    }

    /// <inheritdoc />
    public override async Task<UnlockResponse> TryUnlockAsync(string storeName, string resourceId, string lockOwner,
        CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrEmpty(storeName);
        ArgumentException.ThrowIfNullOrEmpty(resourceId);
        ArgumentException.ThrowIfNullOrEmpty(lockOwner);

        var request = new Autogenerated.UnlockRequest
        {
            StoreName = storeName, ResourceId = resourceId, LockOwner = lockOwner
        };

        Autogenerated.UnlockResponse response;
        try
        {
            var grpcCallOptions =
                DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprDistributedLockClient).Assembly, this.DaprApiToken,
                    cancellationToken);
            response = await this.Client.UnlockAlpha1Async(request, grpcCallOptions);
        }
        catch (RpcException ex)
        {
            throw new DaprException(
                "Lock operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
        }

        var status = GetUnlockStatus(response.Status);
        return new UnlockResponse(status);
    }

    /// <summary>
    /// Maps the <see cref="Status"/> to the equivalent <see cref="LockStatus"/> value.
    /// </summary>
    /// <param name="status">The status value to map.</param>
    /// <returns>The properly mapped value.</returns>
    private static LockStatus GetUnlockStatus(Autogenerated.UnlockResponse.Types.Status status)
        => status switch
        {
            Autogenerated.UnlockResponse.Types.Status.Success => LockStatus.Success,
            Autogenerated.UnlockResponse.Types.Status.LockDoesNotExist => LockStatus.LockDoesNotExist,
            Autogenerated.UnlockResponse.Types.Status.LockBelongsToOthers => LockStatus.LockBelongsToOthers,
            Autogenerated.UnlockResponse.Types.Status.InternalError => LockStatus.InternalError,
            _ => throw new ArgumentOutOfRangeException(nameof(status), status, $"Status '{status}' is not supported")
        };
}
